package Agent;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;

import com.mysql.jdbc.Connection;
import com.mysql.jdbc.ResultSet;
import com.mysql.jdbc.Statement;

import Exception.ExceptionFreeAgent;
import Exception.ExceptionVM;
import MachineLearning.LinearRegression;
import SLA.SLA;

public class ThreadAgent extends Thread{

		//variaveis de conexão com o banco
		private Connection conn;
		private Statement statement;
		private ResultSet result; 
	

		//dados referentes a carga de entrada, consideramos inicialmente uma consulta para cada cliente
		private ArrayList<String> transaction;
		private double timeQueriesAlreadyExecuted; //tempo que levou pra executar a subconsulta
		private int tuplesToReturn;
		private SLA sla;
		private LinearRegression machine;
		private float timeMonitoring;
		private String agentName;
		
		public ThreadAgent(String agentName, ArrayList<String> array, SLA sla, LinearRegression machine) throws ClassNotFoundException, SQLException {
			this.agentName = agentName;
			this.transaction = array;
			this.sla = sla;
			this.machine = machine;
			connectDB();
		}
		
		private void connectDB() throws ClassNotFoundException, SQLException {
			Class.forName("com.mysql.jdbc.Driver");
		    setConn((Connection) DriverManager.getConnection("jdbc:mysql://localhost:3306/tpch?autoReconnect=true","tpch","root"));
		    DriverManager.setLoginTimeout(10000);
		    System.out.println("Agente conectou-se ao banco!");	
		    setStatement((Statement) getConn().createStatement());
		}
		
		private int selectivityWorkload() throws SQLException{
			
			int selectivityWorkload = 0;
			setStatement((Statement) getConn().createStatement());
			int numberQueries = transaction.size();	
			
			for(int i=0; i< numberQueries;i++){
				setStatement((Statement) getConn().createStatement());
				setResult((ResultSet) getStatement().executeQuery("explain "+transaction.get(i)));
				getResult().first();
				selectivityWorkload += machine.theta0 + machine.theta1*getResult().getInt("rows"); 
			}
			return selectivityWorkload;		
		}
		
				@Override
		public void run() {
			System.out.println("Chamada do metodo run!");
			long startTime;
			long finishTime=0;
			String query;
			int selectivity;
			double maximumThr;
			int tuplesAlreadyReturned = 0;
			double avarageFlow;
			double totalTime=0;
			
			Format fmt = Format.getPrettyFormat();
		    fmt.setIndent( " " );

		    try{
		    			    	
	            Document doc = new Document();
	            Element root = new Element("node");
	            doc.setRootElement(root);
	            
	            root.setAttribute("provisionamento", "dinamico");
	            root.setAttribute("id", agentName);
	            //TODO PASSAR COMO PARAMETRO A CONSULTA E O ID DELA
	            root.setAttribute("queryID", "default");
	            
	        	selectivity = selectivityWorkload();
				sla.setSelectivityQ(selectivity);
				System.out.println("Seletividade da consulta Q "+selectivity);
				root.setAttribute("selectivity",new Integer(selectivity).toString());
				
				maximumThr = sla.getMaximumThr();
				System.out.println("Vazao maxima da VM "+maximumThr);
				root.setAttribute("maxThroughput",new Double(maximumThr).toString());
	        	
	            File f = new File(agentName+Math.random()+".xml");
	            FileWriter arquivo = new FileWriter(f);

	            Element e;
	            Element e1;
	            Element e2;
	            							
				e = new Element("workload");		
								
				tuplesToReturn = selectivity;//quantidade de tuplas a serem retornadas
				System.out.println("Tuplas a serem retornadas "+tuplesToReturn);
				e.setAttribute("workloadSelectivity",new Integer(tuplesToReturn).toString());			
				root.addContent(e);
				
				int i = 0, j=0, k=0;
				boolean check=true;
								
				while(!transaction.isEmpty() && check){		
					e1 = new Element("partition");
					e1.setAttribute("id", new Integer(j).toString());
					
					query = transaction.remove(i);
					e1.setAttribute("query", query);
					
					e2 = new Element("beginTime");					
					startTime=System.currentTimeMillis();
					System.out.println("Tempo de inicio da subconsulta "+new Double(0.001*(startTime)));
					e2.addContent(new Double(0.001*(startTime)).toString());
					e1.addContent(e2);
					
					//executa a consulta
					System.out.println("Executando a subconsulta "+query);
					setResult((ResultSet) getStatement().executeQuery(query));
					this.result.last();
					int rows = this.result.getRow();
					tuplesAlreadyReturned+=rows;
					result.close();
					
					System.out.println("Numero de tuplas retornadas "+rows);
					e2 = new Element("returnedTuples");
					e2.addContent(new Integer(rows).toString());
					e1.addContent(e2);
					
					finishTime = System.currentTimeMillis();
					System.out.println("Tempo de fim da subconsulta "+new Double(0.001*(finishTime)));
					e2 = new Element("endTime");
					e2.addContent(new Double(0.001*(finishTime)).toString());
					e1.addContent(e2);
					
					/**************************************************************************/
					
					timeQueriesAlreadyExecuted += new Double(0.001*(finishTime - startTime));
					System.out.println("Tempo ja gasto na execucao das subconsultas "+timeQueriesAlreadyExecuted);
					e2 = new Element("timeQueriesAlreadyExecuted");
					e2.addContent(new Double(timeQueriesAlreadyExecuted).toString());
					e1.addContent(e2);

			
					tuplesToReturn = Math.abs(tuplesToReturn - rows);
					System.out.println("Tuplas a serem retornadas pelas subconsultas restantes "+tuplesToReturn);
					e2 = new Element("expectedTuples");
					e2.addContent(new Integer(tuplesToReturn).toString());
					e1.addContent(e2);

					
					totalTime = (timeMonitoring + (timeQueriesAlreadyExecuted + (tuplesToReturn)/(maximumThr)));
					System.out.println("Tempo total previsto maximo "+totalTime);
					e2 = new Element("timeExpectedTuplesMax");
					e2.addContent(new Double(totalTime).toString());
					e1.addContent(e2);
					
					avarageFlow = selectivity/totalTime;
					System.out.println("Vazao media prevista "+avarageFlow);
					e2 = new Element("avgThroughput");
					e2.addContent(new Double(avarageFlow).toString());
					e1.addContent(e2);
					
					System.out.println("Vazao media real "+tuplesAlreadyReturned/(timeQueriesAlreadyExecuted+timeMonitoring));
					
					e.addContent(e1);
					j++;
					//if(avarageFlow>maximumThr) maximumThr=avarageFlow;
					/************************************************************************/
					
					if(avarageFlow < sla.getSloValue()){
						System.out.println("Tomada de decisao!");
						e1 = new Element("decisionMaking");
						e1.setAttribute("machineCost",new Double(sla.getMachineCost()).toString());
						e1.setAttribute("monitoringCost",new Double(timeMonitoring*sla.getCostMonitoring()).toString());
						root.addContent(e1);
						check=false;										
					}
					else timeMonitoring+=(new Double(0.001*(System.currentTimeMillis()-finishTime)));
							
				}
				
				XMLOutputter xout = new XMLOutputter(fmt);
		        xout.output( doc , arquivo );
		        arquivo.close();
		}catch (SQLException e) {
				e.printStackTrace();
				System.out.println("Erro na consulta de entrada!");
		} catch (IOException e) {
			e.printStackTrace();
		}
		    
	}

		public Connection getConn() {
			return conn;
		}

		public void setConn(Connection conn) {
			this.conn = conn;
		}

		public Statement getStatement() {
			return statement;
		}

		public void setStatement(Statement statement) {
			this.statement = statement;
		}
	

		public ResultSet getResult() {
			return result;
		}

		public void setResult(ResultSet result) {
			this.result = result;
		}
		
		public ArrayList<String> getTransaction() {
			return transaction;
		}

		public double getTimeQueriesAlreadyExecuted() {
			return timeQueriesAlreadyExecuted;
		}

		public int getTuplesToReturn() {
			return tuplesToReturn;
		}

		public SLA getSLA() {
			return sla;
		}

		public float getTimeMonitoring() {
			return timeMonitoring;
		}

		public String getAgentName() {
			return agentName;
		}
	
}
